package chapter5
{
	import com.adobe.utils.*;
	
	import flash.display.*;
	import flash.display.Stage;
	import flash.display3D.*;
	import flash.display3D.textures.*;
	import flash.events.*;
	import flash.geom.*;
	import flash.text.*;
	import flash.utils.*;
	
	import utils.DisplayMolehill;
	import utils.IRender;
	import utils.Molehill_obj_parser;
	
	public class Chapter5Demp extends DisplayMolehill implements IRender
	{
		// used by the GUI
		private var fps_last:uint = getTimer();
		private var fps_ticks:uint = 0;
		private var fps_tf:TextField;
		private var score_tf:TextField;
		private var score:uint = 0;
		
		// constants used during inits
		private const swf_width:int = 640;
		private const swf_height:int = 480;
		// for this demo, ensure ALL textures are 512x512
		private const texture_size:int = 512;
		
		// the 3d graphics window on the stage
		private var context3D:Context3D;
		// the compiled shaders used to render our mesh
		private var shaderProgram1:Program3D;
		private var shaderProgram2:Program3D;
		private var shaderProgram3:Program3D;
		private var shaderProgram4:Program3D;
		
		// the uploaded verteces used by our mesh
		private var vertexBuffer:VertexBuffer3D;
		// the uploaded indeces of each vertex of the mesh
		private var indexBuffer:IndexBuffer3D;
		// the data that defines our 3d mesh model
		private var meshVertexData:Vector.<Number>;
		// the indeces that define what data is used by each vertex
		private var meshIndexData:Vector.<uint>;
		
		// matrices that affect the mesh location and camera angles
		private var projectionmatrix:PerspectiveMatrix3D =
			new PerspectiveMatrix3D();
		private var modelmatrix:Matrix3D = new Matrix3D();
		private var viewmatrix:Matrix3D = new Matrix3D();
		private var terrainviewmatrix:Matrix3D = new Matrix3D();
		private var modelViewProjection:Matrix3D = new Matrix3D();
		
		// a simple frame counter used for animation
		private var t:Number = 0;
		// a reusable loop counter
		private var looptemp:int = 0;
		
		/* TEXTURES: Pure AS3 and Flex version:
		* if you are using Adobe Flash CS5
		* comment out the following: */
		[Embed (source = "assets/art/spaceship_texture.jpg")]
		private var myTextureBitmap:Class;
		private var myTextureData:Bitmap = new myTextureBitmap();
		
		[Embed (source = "assets/art/terrain_texture.jpg")]
		private var terrainTextureBitmap:Class;
		private var terrainTextureData:Bitmap = new terrainTextureBitmap();
		
		// The Molehill Texture that uses the above myTextureData
		private var myTexture:Texture;
		private var terrainTexture:Texture;
		
		// The spaceship mesh data
		[Embed (source = "assets/art/sphere.obj",mimeType = "application/octet-stream")]
		private var my_obj_data:Class;
		private var my_mesh:Molehill_obj_parser;
		// The terrain mesh data
		[Embed (source = "assets/art/terrain.objs",mimeType = "application/octet-stream")]
		private var terrain_obj_data:Class;
		private var terrain_mesh:Molehill_obj_parser;
		
		
		public function Chapter5Demp(stage:Stage)
		{
			super(stage);
			
			// add some text labels
			initGUI();
			// and request a context3D from Molehill
			stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
			stage.stage3Ds[0].requestContext3D();
		}
		
		private function update_score():void
		{
			// for now, you earn points over time
			score++;
			// padded with zeroes
			if (score < 10) score_tf.text = 'Score: 00000' + score;
			else if (score < 100) score_tf.text = 'Score: 0000' + score;
			else if (score < 1000) score_tf.text = 'Score: 000' + score;
			else if (score < 10000) score_tf.text = 'Score: 00' + score;
			else if (score < 100000) score_tf.text = 'Score: 0' + score;
			else score_tf.text = 'Score: ' + score;
		}
		
		private function initGUI():void
		{
			// a text format descriptor used by all gui labels
			var myFormat:TextFormat = new TextFormat();
			myFormat.color = 0xFFFFFF;
			myFormat.size = 13;
			// create an FPSCounter that displays the framerate on screen
			fps_tf = new TextField();
			fps_tf.x = 0;
			fps_tf.y = 0;
			fps_tf.selectable = false;
			fps_tf.autoSize = TextFieldAutoSize.LEFT;
			fps_tf.defaultTextFormat = myFormat;
			fps_tf.text = "Initializing Molehill...";
			stage.addChild(fps_tf);
			// create a score display
			score_tf = new TextField();
			score_tf.x = 560;
			score_tf.y = 0;
			score_tf.selectable = false;
			score_tf.autoSize = TextFieldAutoSize.LEFT;
			score_tf.defaultTextFormat = myFormat;
			score_tf.text = "000000";
			stage.addChild(score_tf);
			// add some labels to describe each shader
			var label1:TextField = new TextField();
			label1.x = 100;
			label1.y = 180;
			label1.selectable = false;
			label1.autoSize = TextFieldAutoSize.LEFT;
			label1.defaultTextFormat = myFormat;
			label1.text = "Shader 1: Textured";
			stage.addChild(label1);
			
			var label2:TextField = new TextField();
			label2.x = 400;
			label2.y = 180;
			label2.selectable = false;
			label2.autoSize = TextFieldAutoSize.LEFT;
			label2.defaultTextFormat = myFormat;
			label2.text = "Shader 2: Vertex RGB";
			stage.addChild(label2);
			
			var label3:TextField = new TextField();
			label3.x = 80;
			label3.y = 440;
			label3.selectable = false;
			label3.autoSize = TextFieldAutoSize.LEFT;
			label3.defaultTextFormat = myFormat;
			label3.text = "Shader 3: Vertex RGB + Textured";
			stage.addChild(label3);
			
			var label4:TextField = new TextField();
			label4.x = 340;
			label4.y = 440;
			label4.selectable = false;
			label4.autoSize = TextFieldAutoSize.LEFT;
			label4.defaultTextFormat = myFormat;
			label4.text = "Shader 4: Textured + setProgramConstants";
			stage.addChild(label4);
		}
		
		public function uploadTextureWithMipmaps(dest:Texture, src:BitmapData):void
		{
			var ws:int = src.width;
			var hs:int = src.height;
			var level:int = 0;
			var tmp:BitmapData;
			var transform:Matrix = new Matrix();
			var tmp2:BitmapData;
			tmp = new BitmapData( src.width, src.height, true, 0x00000000);
			while ( ws >= 1 && hs >= 1 )
			{
				tmp.draw(src, transform, null, null, null, true);
				dest.uploadFromBitmapData(tmp, level);
				transform.scale(0.5, 0.5);
				level++;
				ws >>= 1;
				hs >>= 1;
				if (hs && ws)
				{
					tmp.dispose();
					tmp = new BitmapData(ws, hs, true, 0x00000000);
				}
			}
			tmp.dispose();
		}
		
		private function onContext3DCreate(event:Event):void
		{
			// Remove existing frame handler. Note that a context
			// loss can occur at any time which will force you
			// to recreate all objects we create here.
			// A context loss occurs for instance if you hit
			// CTRL-ALT-DELETE on Windows.
			// It takes a while before a new context is available
			// hence removing the enterFrame handler is important!
			//removeEventListener(Event.ENTER_FRAME,enterFrame);
			// Obtain the current context
			var t:Stage3D = event.target as Stage3D;
			context3D = t.context3D;
			if (context3D == null)
			{
				// Currently no 3d context is available (error!)
				return;
			}
			
			// Disabling error checking will drastically improve performance.
			// If set to true, Flash sends helpful error messages regarding
			// AGAL compilation errors, uninitialized program constants, etc.
			context3D.enableErrorChecking = true;
			// Initialize our mesh data
			initData();
			
			// The 3d back buffer size is in pixels (2=antialiased)
			context3D.configureBackBuffer(swf_width, swf_height, 2, true);
			// assemble all the shaders we need
			initShaders();
			
			myTexture = context3D.createTexture(texture_size, texture_size,Context3DTextureFormat.BGRA, false);
			uploadTextureWithMipmaps(myTexture, myTextureData.bitmapData);
			
			terrainTexture = context3D.createTexture(texture_size, texture_size,Context3DTextureFormat.BGRA, false);
			uploadTextureWithMipmaps(terrainTexture, terrainTextureData.bitmapData);
			
			// create projection matrix for our 3D scene
			projectionmatrix.identity();
			
			// 45 degrees FOV, 640/480 aspect ratio, 0.1=near, 100=far
			projectionmatrix.perspectiveFieldOfViewRH(45.0, swf_width / swf_height, 0.01, 5000.0);
			// create a matrix that defines the camera location
			viewmatrix.identity();
			// move the camera back a little so we can see the mesh
			viewmatrix.appendTranslation(0,0,-3);
			// tilt the terrain a little so it is coming towards us
			terrainviewmatrix.identity();
			terrainviewmatrix.appendRotation(-60,Vector3D.X_AXIS);
						
			this.dispatchEvent(new Event(Event.COMPLETE));
		}
		
		private function initData():void
		{
			// parse the OBJ file and create buffers
			my_mesh = new Molehill_obj_parser(my_obj_data, context3D, 1, true, true);
			// parse the terrain mesh as well
			terrain_mesh = new Molehill_obj_parser(terrain_obj_data, context3D, 1, true, true);
		}
		
		private function initShaders():void{
			// A simple vertex shader which does a 3D transformation
			// for simplicity, it is used by all four shaders
			var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
			vertexShaderAssembler.assemble
				(
					Context3DProgramType.VERTEX,
					// 4x4 matrix multiply to get camera angle
					"m44 op, va0, vc0\n" +
					// tell fragment shader about XYZ
					"mov v0, va0\n" +
					// tell fragment shader about UV
					"mov v1, va1\n" +
					// tell fragment shader about RGBA
					"mov v2, va2\n"
				);
			
			// textured using UV coordinates
			var fragmentShaderAssembler1:AGALMiniAssembler = new AGALMiniAssembler();
			fragmentShaderAssembler1.assemble
				(
					Context3DProgramType.FRAGMENT,
					// grab the texture color from texture 0
					// and uv coordinates from varying register 1
					// and store the interpolated value in ft0
					"tex ft0, v1, fs0 <2d,repeat,miplinear>\n" +
					// move this value to the output color
					"mov oc, ft0\n"
				);
			
			// no texture, RGBA from the vertex buffer data
			var fragmentShaderAssembler2:AGALMiniAssembler = new AGALMiniAssembler();
			fragmentShaderAssembler2.assemble
				(
					Context3DProgramType.FRAGMENT,
					// grab the color from the v2 register
					// which was set in the vertex program
					//"sub ft0, v2, fc1\n" +
					"mov oc, v2\n"
				);
			
			// textured using UV coordinates AND colored by vertex RGB
			var fragmentShaderAssembler3:AGALMiniAssembler = new AGALMiniAssembler();
			fragmentShaderAssembler3.assemble
				(
					Context3DProgramType.FRAGMENT,
					// grab the texture color from texture 0
					// and uv coordinates from varying register 1
					"tex ft0, v1, fs0 <2d,repeat,miplinear>\n" +
					// multiply by the value stored in v2 (the vertex rgb)
					"mul ft1, v2, ft0\n" +
					// move this value to the output color
					"mov oc, ft1\n"
				);
			
			// textured using UV coordinates and
			// tinted using a fragment constant
			var fragmentShaderAssembler4:AGALMiniAssembler = new AGALMiniAssembler();
			fragmentShaderAssembler4.assemble
				(
					Context3DProgramType.FRAGMENT,
					// grab the texture color from texture 0
					// and uv coordinates from varying register 1
					"tex ft0, v1, fs0 <2d,repeat,miplinear>\n" +
					// multiply by the value stored in fc0
					"mul ft1, fc0, ft0\n" +
					// move this value to the output color
					"mov oc, ft1\n"
				);
			
			// combine shaders into a program which we then upload to the GPU
			shaderProgram1 = context3D.createProgram();
			shaderProgram1.upload(vertexShaderAssembler.agalcode,fragmentShaderAssembler1.agalcode);
			
			shaderProgram2 = context3D.createProgram();
			shaderProgram2.upload(vertexShaderAssembler.agalcode,fragmentShaderAssembler2.agalcode);
			
			shaderProgram3 = context3D.createProgram();
			shaderProgram3.upload(vertexShaderAssembler.agalcode,fragmentShaderAssembler3.agalcode);
			
			shaderProgram4 = context3D.createProgram();
			shaderProgram4.upload(vertexShaderAssembler.agalcode,fragmentShaderAssembler4.agalcode);
			
		}
		
		private function render_terrain():void
		{
			// draw the terrain mesh twice ("infinite loop")
			context3D.setTextureAt(0, terrainTexture);
			// simple textured shader
			context3D.setProgram ( shaderProgram1 );
			// position
			context3D.setVertexBufferAt(0, terrain_mesh.positionsBuffer,
				0, Context3DVertexBufferFormat.FLOAT_3);
			// tex coord
			context3D.setVertexBufferAt(1, terrain_mesh.uvBuffer,
				0, Context3DVertexBufferFormat.FLOAT_2);
			// vertex rgba
			context3D.setVertexBufferAt(2, terrain_mesh.colorsBuffer,
				0, Context3DVertexBufferFormat.FLOAT_4);
			// set up camera angle
			modelmatrix.identity();
			// make the terrain face the right way
			modelmatrix.appendRotation( -90, Vector3D.Y_AXIS);
			// slowly move the terrain around
			modelmatrix.appendTranslation(
				Math.cos(t/300)*1000,Math.cos(t/200)*1000 + 500,-130);
			// clear the matrix and append new angles
			modelViewProjection.identity();
			modelViewProjection.append(modelmatrix);
			modelViewProjection.append(terrainviewmatrix);
			modelViewProjection.append(projectionmatrix);
			// pass our matrix data to the shader program
			context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0, modelViewProjection, true );
			context3D.drawTriangles(terrain_mesh.indexBuffer,0, terrain_mesh.indexBufferCount);
		}
		
		public function render():void
		{
			// clear scene before rendering is mandatory
			context3D.clear(0,0,0);
			
			// move or rotate more each frame
			t += 2.0;
			// scroll and render the terrain once
			render_terrain();
			// how far apart each of the 4 spaceships is
			var dist:Number = 0.8;
			// loop through each mesh we want to draw
			for (looptemp = 0; looptemp < 4; looptemp++)
			{
				// clear the transformation matrix to 0,0,0
				modelmatrix.identity();
				// each mesh has a different texture,
				// shader, position and spin speed
				switch(looptemp)
				{
					case 0:
						context3D.setTextureAt(0, myTexture);
						context3D.setProgram ( shaderProgram1 );
						modelmatrix.appendRotation(t*0.7, Vector3D.Y_AXIS);
						modelmatrix.appendRotation(t*0.6, Vector3D.X_AXIS);
						modelmatrix.appendRotation(t*1.0, Vector3D.Y_AXIS);
						modelmatrix.appendTranslation(-dist, dist, 0);
						break;
					case 1:
						context3D.setTextureAt(0, null);
						context3D.setProgram ( shaderProgram2 );
						modelmatrix.appendRotation(t*-0.2, Vector3D.Y_AXIS);
						modelmatrix.appendRotation(t*0.4, Vector3D.X_AXIS);
						modelmatrix.appendRotation(t*0.7, Vector3D.Y_AXIS);
						modelmatrix.appendTranslation(dist, dist, 0);
						break;
					case 2:
						context3D.setTextureAt(0, myTexture);
						context3D.setProgram ( shaderProgram3 );
						modelmatrix.appendRotation(t*1.0, Vector3D.Y_AXIS);
						modelmatrix.appendRotation(t*-0.2, Vector3D.X_AXIS);
						modelmatrix.appendRotation(t*0.3, Vector3D.Y_AXIS);
						modelmatrix.appendTranslation(-dist, -dist, 0);
						break;
					case 3:
						context3D.setProgramConstantsFromVector(
							Context3DProgramType.FRAGMENT, 0, Vector.<Number>
							([ 1, Math.abs(Math.cos(t/50)), 0, 1 ]) );
						context3D.setTextureAt(0, myTexture);
						context3D.setProgram ( shaderProgram4 );
						modelmatrix.appendRotation(t*0.3, Vector3D.Y_AXIS);
						modelmatrix.appendRotation(t*0.3, Vector3D.X_AXIS);
						modelmatrix.appendRotation(t*-0.3, Vector3D.Y_AXIS);
						modelmatrix.appendTranslation(dist, -dist, 0);
						break;
				}
				// clear the matrix and append new angles
				modelViewProjection.identity();
				modelViewProjection.append(modelmatrix);
				modelViewProjection.append(viewmatrix);
				modelViewProjection.append(projectionmatrix);
				// pass our matrix data to the shader program
				context3D.setProgramConstantsFromMatrix(
					Context3DProgramType.VERTEX,
					0, modelViewProjection, true );
				// draw a spaceship mesh
				// position
				
				context3D.setVertexBufferAt(0, my_mesh.positionsBuffer,
					0, Context3DVertexBufferFormat.FLOAT_3);
				// tex coord
				context3D.setVertexBufferAt(1, my_mesh.uvBuffer,
					0, Context3DVertexBufferFormat.FLOAT_2);
				// vertex rgba
				context3D.setVertexBufferAt(2, my_mesh.colorsBuffer,
					0, Context3DVertexBufferFormat.FLOAT_4);
				
				context3D.drawTriangles(my_mesh.indexBuffer,
					0, my_mesh.indexBufferCount);
			}
			
			// present/flip back buffer
			// now that all meshes have been drawn
			context3D.present();
			// update the FPS display
			fps_ticks++;
			var now:uint = getTimer();
			var delta:uint = now - fps_last;
			// only update the display once a second
			if (delta >= 1000)
			{
				var fps:Number = fps_ticks / delta * 1000;
				fps_tf.text = fps.toFixed(1) + " fps";
				fps_ticks = 0;
				fps_last = now;
			}
			// update the rest of the GUI
			update_score();
			
			
			
		}
	}
}